/********************************************************************
	created:	2012/02/02
	filename: 	StageCollision.cpp
	author:		Mwolf
	
	purpose:	
*********************************************************************/

#include "StageCollision.h"

#include "GameStage.h"
#include "Actor.h"
#include "DebugDraw.h"

#include <vector>
#include <list>
#include <algorithm>


Vector2 Col_GetTransformedPoint( const Vector2& point, float depth, const Matrix4& transform )
{
	Vector3 worldPoint = Vector3( point, depth );
	Vector3 postProjPoint = Vec3_Transform( worldPoint, transform );

	return Vector2( postProjPoint.x, postProjPoint.y );
}

BoundBox Col_GetTransformedBound( const BoundBox& box, float depth, const Matrix4& transform )
{
#if 0
	BoundBox output = box;
	T3Camera* camera = KleinGame()->GetCamera();
	float p = T3Camera_GetAnimationProgress( camera );
	T3CameraTilt tilt = camera->tilt;

	switch (tilt)
	{
	case T3_CAMERA_TILT_LEFT:
		output.xMin -= p;
		output.Translate(Vector2(-p, 0.f) * depth);
		break;
	case T3_CAMERA_TILT_RIGHT:
		output.xMax += p;
		output.Translate(Vector2(p, 0.f) * depth);
		break;
	case T3_CAMERA_TILT_UP:
		output.yMax += p;
		output.Translate(Vector2(0.f, p) * depth);
		break;
	case T3_CAMERA_TILT_DOWN:
		output.yMin -= p;
		output.Translate(Vector2(0.f, -p) * depth);
		break;
	default:
		break;
	}
#else

	//Vector3 vMin = Vector3( box.vMin(), depth ),
	//		vMax = Vector3( box.vMax(), depth );

	//vMin = Transform( vMin, KleinGame()->GetSceneTransform() );
	//vMax = Transform( vMax, KleinGame()->GetSceneTransform() );

	//if (vMin.x > vMax.x) std::swap(vMin.x, vMax.x);
	//if (vMin.y > vMax.y) std::swap(vMin.y, vMax.y);
	//if (vMin.z > vMax.z) std::swap(vMin.z, vMax.z);

	Vector3 v[8] =
	{
		Vector3( box.xMin, box.yMin, depth ),
		Vector3( box.xMax, box.yMin, depth ),
		Vector3( box.xMin, box.yMax, depth ),
		Vector3( box.xMax, box.yMax, depth ),

		Vector3( box.xMin, box.yMin, depth + 1.f ),
		Vector3( box.xMax, box.yMin, depth + 1.f ),
		Vector3( box.xMin, box.yMax, depth + 1.f ),
		Vector3( box.xMax, box.yMax, depth + 1.f ),
	};

	Vector3 vMin, vMax;

	for (int i=0; i<8; i++)
	{
		Vector3 point = Vec3_Transform( v[i], transform );
		if (i == 0)
		{
			vMin = point;
			vMax = point;
		}
		else
		{
			if (point.x < vMin.x) vMin.x = point.x;
			if (point.y < vMin.y) vMin.y = point.y;
			if (point.z < vMin.z) vMin.z = point.z;

			if (point.x > vMax.x) vMax.x = point.x;
			if (point.y > vMax.y) vMax.y = point.y;
			if (point.z > vMax.z) vMax.z = point.z;
		}
	}

	BoundBox output( vMin.x, vMin.y, vMax.x, vMax.y );
#endif
	return output;
}

struct SimpleGeom
{
	BoundBox	LastFrame;
	BoundBox	ThisFrame;
	//GW_INT8		Depth;
};

std::list<SimpleGeom> CollisionGeomList;

#define FLOAT_EQUAL(a, b) ( fabs(b - a) < 0.00001f )

static bool Col_MergeBounds( const BoundBox& bound1, const BoundBox& bound2, BoundBox* output, const Vector2* ignoredGapMinSize = NULL )
{
	if (!output) return false;

	if ( FLOAT_EQUAL(bound1.xMax, bound2.xMax) &&
		 FLOAT_EQUAL(bound1.xMin, bound2.xMin) )
	{
		if ( (bound1.yMin>=bound2.yMin && bound1.yMin<=bound2.yMax) ||
			 (bound1.yMax>=bound2.yMin && bound1.yMax<=bound2.yMax) ||
			 (ignoredGapMinSize && GW_MATH_MIN( abs(bound2.yMin - bound1.yMax), abs(bound2.yMax - bound1.yMin) ) < ignoredGapMinSize->y ) )
		{
			*output = BoundBox::Merge(bound1, bound2);
			return true;
		}
	}
	else if ( FLOAT_EQUAL(bound1.yMax, bound2.yMax) &&
			  FLOAT_EQUAL(bound1.yMin, bound2.yMin) )
	{
		if ( (bound1.xMin>=bound2.xMin && bound1.xMin<=bound2.xMax) ||
			 (bound1.xMax>=bound2.xMin && bound1.xMax<=bound2.xMax) ||
			 (ignoredGapMinSize && GW_MATH_MIN( abs(bound2.xMin - bound1.xMax), abs(bound2.xMax - bound1.xMin) ) < ignoredGapMinSize->x ) )
		{
			*output = BoundBox::Merge(bound1, bound2);
			return true;
		}
	}

	return false;
}

// Merge two simple geometries
static bool Col_MergeGeoms( const SimpleGeom& geom1, const SimpleGeom& geom2, SimpleGeom* output, const Vector2* ignoredGapMinSize = NULL )
{
	if (!output) return false;
	//if (geom1.Depth != geom2.Depth) return false;

	BoundBox lastFrame, thisFrame;
	if ( Col_MergeBounds(geom1.LastFrame, geom2.LastFrame, &lastFrame, ignoredGapMinSize) &&
		 Col_MergeBounds(geom1.ThisFrame, geom2.ThisFrame, &thisFrame, ignoredGapMinSize) )
	{
		output->LastFrame = lastFrame;
		output->ThisFrame = thisFrame;
		//output->Depth = geom1.Depth;
		return true;
	}

	return false;
}

static void Col_AddCollisionStageGeom( STAGE_GEOM* geom )
{
	SimpleGeom sgeom;
	sgeom.LastFrame = geom->post_proj_bound_last_frame;
	sgeom.ThisFrame = geom->post_proj_bound;
	//sgeom.Depth = (GW_INT8)geom->depth;

	CollisionGeomList.push_back( sgeom );
}

static void Col_ClearCollisionGeomList()
{
	CollisionGeomList.clear();
}

static int Col_MergeCollisionGeoms( const Vector2* ignoredGapMinSize = NULL )
{
	int count = 0;

	std::list<SimpleGeom>::iterator iter, iterMerge;
	for ( iter=CollisionGeomList.begin(); iter!=CollisionGeomList.end(); iter++ )
	{
		iterMerge = iter;
		iterMerge++;

		for ( ; iterMerge!=CollisionGeomList.end(); iterMerge++ )
		{
			SimpleGeom geom;

			if ( Col_MergeGeoms(*iter, *iterMerge, &geom, ignoredGapMinSize ) )
			{
				count++;

				// Delete merged elements
				(*iter) = geom;
				CollisionGeomList.erase( iterMerge );
				iterMerge = iter;
			}
		}
	}

	return count;
}

static void Col_DrawCollisionGeoms()
{
	std::list<SimpleGeom>::iterator iter;
	for ( iter=CollisionGeomList.begin(); iter!=CollisionGeomList.end(); iter++ )
	{
		DebugDrawList_DrawBoundingBox( iter->ThisFrame, GWColor::RED );
	}
}

void Col_UpdateActorPositionByCollisionWithStage( const GameStage* stage, Actor* actor )
{
	bool result = false;
	Vector2 rel = actor->MoveController() + actor->Velocity();
	actor->UpdatePostProjBoundWithCameraTilt( rel );

	Col_ClearCollisionGeomList();

	std::vector<STAGE_GEOM*> col_group;
	STAGE_GEOM* geom;

	// Collect collision objects
	for (geom = StageGeom_GetFirst(); geom != NULL; geom = StageGeom_GetNext(geom))
	{
		bool collide = false;

		// Ignore non-solid geometries
		if ( stage->GetStageGeomUsage(geom) != TILE_USAGE_SOLID )
			continue;

		if ( actor->TestCollisionWithCameraTilt( geom->post_proj_bound ) )
			collide = true;

		if (collide)
		{
			col_group.push_back(geom);

			Col_AddCollisionStageGeom( geom );
		}

#if defined T3_DEBUG_DRAW_COLLISION
		geom->draw_collision |= collide;
#endif	// #if defined T3_DEBUG_DRAW_COLLISION
	}

	Vector2 ActorSize = Vector2( actor->GetSize(), actor->GetSize() );

	// Merge collision geometries
	while ( Col_MergeCollisionGeoms( &ActorSize ) ) {}

	// Draw merged geometries
	Col_DrawCollisionGeoms();

	// Evaluate movement vector with collisions
	std::list<SimpleGeom>::iterator iter;
	for ( iter=CollisionGeomList.begin(); iter!=CollisionGeomList.end(); iter++ )
	{
		result |= actor->DoCollisionMove( iter->LastFrame, iter->ThisFrame, 0, 
										  rel, &rel );
	}

	// Finally, we put actor on the correct layer
	GW_INT8 minDepth = 127;
	bool needAdjustDepth = false;
	Vector2 floorOffset = rel;
	floorOffset.y -= KLEIN_COLLISION_TOLERANCE * 2;

	BoundBox floorTestBox = actor->GetTranslatedBound( floorOffset );

	std::vector<STAGE_GEOM*>::iterator colGroupIter;
	for ( colGroupIter=col_group.begin(); colGroupIter!=col_group.end(); colGroupIter++ )
	{
		if ( floorTestBox.TestBoxCollision( (*colGroupIter)->post_proj_bound ) )
		{
			if ( (*colGroupIter)->depth < minDepth )
			{
				minDepth = (GW_INT8)(*colGroupIter)->depth;
				needAdjustDepth = true;
			}
		}
	}

	if ( needAdjustDepth )
		actor->AdjustDepthValue( minDepth );

	// Do actual translation
	actor->TranslateWithCameraTilt( rel );
}
